/*!
 * @file shader.cpp
 * @brief Ciało shader.hh
 */

#include "engine/basic/shader.hh"
using namespace std;

/*--------------------------------------------------------------------------*/

shader::shader(string name) :
	programHandle(glCreateProgram()), name(name){
}

/*--------------------------------------------------------------------------*/

void shader::addShader(std::string path, _shader_type type) {
	GLuint newshader;

	if (type == ST_V) {
		newshader = glCreateShader(GL_VERTEX_SHADER);
	} else {
		newshader = glCreateShader(GL_FRAGMENT_SHADER);
	}

	stringstream ss;
	char tmpt[128];
	string content;

	ifstream file(path.c_str());

	while (file.getline(tmpt, 128)) {
		ss << tmpt << '\n';
	}
	file.close();

	content = ss.str();

	const char *str = content.c_str();
	int is_compiled;

	glShaderSource(newshader, 1, &str, 0);
	glCompileShader(newshader);

	glGetShaderiv(newshader, GL_COMPILE_STATUS, &is_compiled);

	if (is_compiled == false) {
		cout << path << endl << "Błąd podczas kompilacji. Zawartość: " << endl
				<< content;

		pawirr::quit(QBRUTAL);
	}

	glAttachShader(this->programHandle, newshader);

	this->shadersList.push_back(pair<std::string, GLuint> (path, newshader));
}

/*--------------------------------------------------------------------------*/

void shader::linkProgram() {
	glLinkProgram(this->programHandle);

	int IsLinked;
	int maxLength;
	char *shaderProgramInfoLog;

	glGetProgramiv(this->programHandle, GL_LINK_STATUS, (int *) &IsLinked);

	//todo wali C. trza na c++ przerobić
	if (IsLinked == false) {
		/* Noticed that glGetProgramiv is used to get the length for a shader program, not glGetShaderiv. */
		glGetProgramiv(this->programHandle, GL_INFO_LOG_LENGTH, &maxLength);

		/* The maxLength includes the NULL character */
		shaderProgramInfoLog = (char *) malloc(maxLength);

		/* Notice that glGetProgramInfoLog, not glGetShaderInfoLog. */
		glGetProgramInfoLog(this->programHandle, maxLength, &maxLength,
				shaderProgramInfoLog);

		/* Handle the error in an appropriate way such as displaying a message or writing to a log_add file. */
		cout << "ERROR w " << this->name << shaderProgramInfoLog << endl;

		/* In this simple program, we'll just leave */
		free(shaderProgramInfoLog);
		return;
	}
}

/*--------------------------------------------------------------------------*/

string shader::getLog() const {

	//todo dopisać
	return "TODO";
}

/*--------------------------------------------------------------------------*/

void shader::bind() {
	glUseProgram(this->programHandle);

	GLint obj;

	/*
	 * FloatPtr
	 */
	for (list<shaderUniform<float*>*>::iterator Iter =
			this->listFloatPtr.begin(); Iter != this->listFloatPtr.end(); ++Iter) {

		obj = glGetUniformLocation(this->programHandle, (*Iter)->name.c_str());

		if (obj == -1){
			//cout << "Wywalam " << (*Iter)->name << endl;
			this->listFloatPtr.erase(Iter);
			--Iter;
			continue;
		}

		switch ((*Iter)->type) {
		case SUT_1:
			glUniform1f(obj, *(*Iter)->a);
			break;
		case SUT_2:
			glUniform2f(obj, *(*Iter)->a, *(*Iter)->b);
			break;
		case SUT_3:
			glUniform3f(obj, *(*Iter)->a, *(*Iter)->b, *(*Iter)->c);
			break;
		case SUT_4:
			glUniform4f(obj, *(*Iter)->a, *(*Iter)->b, *(*Iter)->c, *(*Iter)->d);
			break;
		}
	}

	/*
	 * IntPtr
	 */
	for (list<shaderUniform<int*>*>::iterator Iter =
			this->listIntPtr.begin(); Iter != this->listIntPtr.end(); ++Iter) {

		obj = glGetUniformLocation(this->programHandle, (*Iter)->name.c_str());

		if (obj == -1){
			//cout << "Wywalam " << (*Iter)->name << endl;
			this->listIntPtr.erase(Iter);
			--Iter;
			continue;
		}

		switch ((*Iter)->type) {
		case SUT_1:
			glUniform1i(obj, *(*Iter)->a);
			break;
		case SUT_2:
			glUniform2i(obj, *(*Iter)->a, *(*Iter)->b);
			break;
		case SUT_3:
			glUniform3i(obj, *(*Iter)->a, *(*Iter)->b, *(*Iter)->c);
			break;
		case SUT_4:
			glUniform4i(obj, *(*Iter)->a, *(*Iter)->b, *(*Iter)->c, *(*Iter)->d);
			break;
		}
	}

	/*
	 * Float
	 */
	for (list<shaderUniform<float>*>::iterator Iter =
			this->listFloat.begin(); Iter != this->listFloat.end(); ++Iter) {

		obj = glGetUniformLocation(this->programHandle, (*Iter)->name.c_str());

		if (obj == -1){
			//cout << "Wywalam " << (*Iter)->name << endl;
			this->listFloat.erase(Iter);
			--Iter;
			continue;
		}

		glUniform4f(obj, (*Iter)->a, (*Iter)->b, (*Iter)->c, (*Iter)->d);
	}

	/*
	 * Int
	 */
	for (list<shaderUniform<int>*>::iterator Iter = this->listInt.begin(); Iter
			!= this->listInt.end(); ++Iter) {

		obj = glGetUniformLocation(this->programHandle, (*Iter)->name.c_str());

		if (obj == -1){
			//cout << "Wywalam " << (*Iter)->name << endl;
			this->listInt.erase(Iter);
			--Iter;
			continue;
		}


		glUniform4i(obj, (*Iter)->a, (*Iter)->b, (*Iter)->c, (*Iter)->d);
	}

	/*
	 * Tekstury
	 */
	int texIndex = 0;
	for (list<pair< std::string, texture*> >::iterator Iter = this->listTextures.begin(); Iter
			!= this->listTextures.end(); ++Iter) {

		obj = glGetUniformLocation(this->programHandle, (*Iter).first.c_str());

		if (obj != -1) {
			glUniform1i(obj, texIndex);
			(*Iter).second->bind(texIndex++);
		}
		else{
			//cout << "Wywalam " << (*Iter).first.c_str() << endl;
			this->listTextures.erase(Iter);
			--Iter;
		}
	}
}

/*--------------------------------------------------------------------------*/

void shader::unbind() {
	glUseProgram(0);
}

/*--------------------------------------------------------------------------*/

GLint shader::getProgram() const {
	return this->programHandle;
}

/*--------------------------------------------------------------------------*/

void shader::addUniformf(std::string name, float *a, float *b, float *c,
		float *d) {

	_shader_uniform_type sut;

	if (d != NULL) {
		sut = SUT_4;
	} else if (c != NULL) {
		sut = SUT_3;
	} else if (b != NULL) {
		sut = SUT_2;
	} else {
		sut = SUT_1;
	}

	shaderUniform<float*> *su = new shaderUniform<float*> (name, sut);

	su->a = a;
	su->b = b;
	su->c = c;
	su->d = d;

	this->listFloatPtr.push_back(su);
}

/*--------------------------------------------------------------------------*/

void shader::addUniformf(std::string name, float a, float b, float c, float d) {

	shaderUniform<float> *su = new shaderUniform<float> (name, SUT_C);

	su->a = a;
	su->b = b;
	su->c = c;
	su->d = d;

	this->listFloat.push_back(su);
}

/*--------------------------------------------------------------------------*/

void shader::addUniformi(std::string name, int *a, int *b, int *c, int *d) {

	_shader_uniform_type sut;

	if (d != NULL) {
		sut = SUT_4;
	} else if (c != NULL) {
		sut = SUT_3;
	} else if (b != NULL) {
		sut = SUT_2;
	} else {
		sut = SUT_1;
	}

	shaderUniform<int*> *su = new shaderUniform<int*> (name, sut);

	su->a = a;
	su->b = b;
	su->c = c;
	su->d = d;

	this->listIntPtr.push_back(su);
}

/*--------------------------------------------------------------------------*/

void shader::addUniformi(std::string name, int a, int b, int c, int d) {

	shaderUniform<int> *su = new shaderUniform<int> (name, SUT_C);

	su->a = a;
	su->b = b;
	su->c = c;
	su->d = d;

	this->listInt.push_back(su);
}

/*--------------------------------------------------------------------------*/

void shader::addTexture(std::string name, texture *tx){
	this->listTextures.push_back(pair<string, texture*>(name, tx));
}

/*--------------------------------------------------------------------------*/

void shader::addAttrib(string name, int id){
	glBindAttribLocation(this->programHandle, id, name.c_str());
}
